package se.webbzon.oschi01.worldarea;

import java.util.Collection;

import javax.media.j3d.BranchGroup;
import javax.media.j3d.Group;
import javax.media.j3d.Node;
import javax.media.j3d.SharedGroup;

import se.webbzon.oschi01.friendly.Friendly;
import se.webbzon.oschi01.j3d.AutoOrderedGroup;
import se.webbzon.oschi01.worldcamera.WorldCameraPane;
import se.webbzon.oschi01.worldobject.WorldObject;
import se.webbzon.oschi01.worldobject.WorldStaticObject;
import se.webbzon.oschi01.worldobject.appearance.Appearance3D;

public class WorldAppearance3D {
	
	// The properties that can be set for the root appearance 
	public final static int	RENDERING_ORDER	= (1<<0);
	
	// The properties of this root appearance
	private final int properties;
	
	// The world attached to this world appearance
	private final WorldArea world;
	
	// The physical universe of the world area (the root node)
	private final SharedGroup physicalUniverse;
	
	// The group in which instances are added to
	private final Group instanceGroup;
	
	/*============================================================
	Constructors
	============================================================*/
	
	/** Creates a new root appearance. This root is the visual representation
	 * of the content in the world area. This class is strictly bounded to the
	 * Java3D environment and is therefore protected by the friendly annotation. **/
	@Friendly WorldAppearance3D(int worldAreaViewProperties, WorldArea world) {
		properties = worldAreaViewProperties;
		this.world = world;
		physicalUniverse = new SharedGroup();
		if (isSet(properties,RENDERING_ORDER)) {
			instanceGroup = new AutoOrderedGroup();
			physicalUniverse.addChild(instanceGroup);
		} else
			instanceGroup = physicalUniverse;
		
		instanceGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		instanceGroup.setCapability(Group.ALLOW_CHILDREN_READ);
		instanceGroup.setCapability(Group.ALLOW_CHILDREN_WRITE);
		
		//addAllInstances();
		world.addListener(new WorldAreaListenerImpl());
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Performs the onPreDraw event for all instances in the
	 * attached world area. **/
	public void performPreDraw(WorldCameraPane background) {
		world.performPreDraw(background);
	}
	
	/** Performs the onPreDraw event for all instances in the
	 * attached world area. **/
	public void performPostDraw(WorldCameraPane hud) {
		world.performPostDraw(hud);
	}
	
	/** Returns the Java3D shared group containing the visual representation
	 *  of the world area associated with this root. **/
	public SharedGroup getSharedGroup() {
		return physicalUniverse;
	}
	
	/*============================================================
	Private Methods
	============================================================*/
	
	/** Returns true if the given property is set in the given properties. **/
	private static final boolean isSet(int properties, int property) {
		return ((properties & property) == property);
	}
	
	/** Adds all instances in the wrapped world area into this world appearance. **/
	private final void addAllInstances() {
		Collection<WorldStaticObject> instances = world.findInstances(WorldStaticObject.class);
		for (WorldStaticObject instance : instances) {
			addInstance(instance);
		}
	}
	
	/** Adds a static world object to this world appearance. **/
	private final void addInstance(WorldStaticObject instance) {
		final Appearance3D appearance = instance.getAppearance();
		final BranchGroup branchGroup = appearance.getJ3dBranch();
		
		// Check if the instance has an appearance
		if (branchGroup == null)
			return;
		
		// Compile branch if it not compiled
		if (!branchGroup.isCompiled())
			branchGroup.compile();
		
		// Add the branch group to the physical universe
		if (branchGroup.getParent() != null) {
			System.out.println(branchGroup + " " + branchGroup.getParent());
			System.out.println(instance);
		}
		if (isSet(properties,RENDERING_ORDER))
			((AutoOrderedGroup) instanceGroup).addChild(
					branchGroup,appearance.getRenderingOrder());
		else
			instanceGroup.addChild(branchGroup);
	}
	
	/** Removes a static world object from this world appearance. **/
	private final void removeInstance(WorldStaticObject instance) {
		final BranchGroup branchGroup = instance.getAppearance().getJ3dBranch();
		
		// Check if the instance has an appearance
		if (branchGroup == null)
			return;
		
		// Remove the branch group from the physical universe
		instanceGroup.removeChild(branchGroup);
	}
	
	private final void clearInstances() {
		System.out.println("Cleared " + instanceGroup);
		Node[] n = new Node[instanceGroup.numChildren()];
		for (int i = 0; i < n.length; i++)
			n[i] = instanceGroup.getChild(i);
		instanceGroup.removeAllChildren();
		for (int i = 0; i < n.length; i++)
			System.out.println("Parent after removal " + n[i].getParent() + " for " + n[i]);
	}
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of a WorldAreaListener. **/
	private class WorldAreaListenerImpl implements WorldAreaListener {

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void onInstanceAdd(WorldArea source, WorldObject instance) {
			if (instance instanceof WorldStaticObject)
				addInstance((WorldStaticObject) instance);
		}

		@Override public void onInstanceRemove(WorldArea source, WorldObject instance) {
			if (instance instanceof WorldStaticObject)
				removeInstance((WorldStaticObject) instance);
		}
		
		@Override public void onInstancesClear(WorldArea source) {
			clearInstances();
		}

		@Override public void onGoto(WorldArea source, WorldArea destination) {
			/*if (destination == world && instanceGroup.numChildren() == 0) {
				addAllInstances();
			} else {
				clearInstances();
			}*/
		}
		
	}

}
